package it.polimi.traveldream.ejb.eaos;

import java.math.BigDecimal;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import it.polimi.traveldream.ejbclient.dtos.BaseProductDTO;
import it.polimi.traveldream.ejbclient.dtos.ExcursionDTO;
import it.polimi.traveldream.ejbclient.dtos.HotelDTO;
import it.polimi.traveldream.ejbclient.dtos.TransportDTO;
import it.polimi.traveldream.ejbclient.dtos.TravelPackageDTO;
import it.polimi.traveldream.ejbclient.eaos.AccountServiceEAO;
import it.polimi.traveldream.ejbclient.eaos.BaseProductServiceEAO;
import it.polimi.traveldream.ejbclient.eaos.PackageCompositionServiceEAO;
import it.polimi.traveldream.ejbclient.eaos.ParticipationServiceEAO;
import it.polimi.traveldream.ejbclient.eaos.TravelPackageServiceEAO;
import it.polimi.traveldream.utils.enums.BaseProductType;
import it.polimi.traveldream.utils.enums.PackageType;
import it.polimi.traveldream.utils.enums.UserRole;
import it.polimi.traveldream.utils.exceptions.NotAllDataException;
import it.polimi.traveldream.utils.exceptions.NotAvailableException;
import it.polimi.traveldream.utils.exceptions.NotCoherentException;
import it.polimi.traveldream.utils.exceptions.NotSuchProductException;
import it.polimi.traveldream.utils.exceptions.NotSuchUserException;

import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 * Session Bean implementation class PackageCompositionService
 */
@Stateful
public class PackageCompositionService implements PackageCompositionServiceEAO {
	@PersistenceContext
	private EntityManager entityManager;
	@EJB
	private AccountServiceEAO accountService;
	@EJB
	private TravelPackageServiceEAO travelPackageService;
	@EJB
	private BaseProductServiceEAO baseProductService;
	@EJB
	private ParticipationServiceEAO participationService;
	private TravelPackageDTO travelPackage;
	private int reservations;

	@RolesAllowed({UserRole._CUSTOMER})
	@Override
	public TravelPackageDTO initCustomComposition(int predefinedPackageId) throws NotSuchProductException {
		travelPackage = travelPackageService.getPackage(predefinedPackageId);
		if(travelPackage.getType() == PackageType.PREDEFINED) {
			travelPackage.initBaseProductList();
			travelPackage.setType(PackageType.CUSTOM);
			reservations = 0;
			return travelPackage;
		} else throw new NotSuchProductException();
	}
	
	@RolesAllowed({UserRole._EMPLOYEE})
	@Override
	public void initPredefinedComposition() {
		travelPackage = new TravelPackageDTO();
		travelPackage.initBaseProductList();
		travelPackage.setType(PackageType.PREDEFINED);
		reservations = 0;
	}
	
	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public void putProduct(BaseProductDTO product) {
		if(product instanceof HotelDTO) {
			travelPackage.addHotel((HotelDTO) product);
		} else if(product instanceof TransportDTO) {
			travelPackage.addTransport((TransportDTO) product);
		} else if(product instanceof ExcursionDTO) {
			travelPackage.addExcursion((ExcursionDTO) product);
		}
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public void pullProduct(BaseProductDTO product) {
		if(product instanceof HotelDTO) {
			travelPackage.removeHotel((HotelDTO) product);
		} else if(product instanceof TransportDTO) {
			travelPackage.removeTransport((TransportDTO) product);
		} else if(product instanceof ExcursionDTO) {
			travelPackage.removeExcursion((ExcursionDTO) product);
		}
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public void setBaseData(Date start, Date end, int reservations)
			throws NotCoherentException, NotAllDataException {
		if(isTemporalIntervalTripCoherent(start, end)) {
			travelPackage.setStartTrip(start);
			travelPackage.setEndTrip(end);
			this.reservations = reservations;
		} else throw new NotCoherentException();
	}
	
	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public void setDescription(String description) {
		travelPackage.setDescription(description);
	}

	@RolesAllowed({UserRole._CUSTOMER})
	@Override
	public int composeCustomPackage()
			throws NotAvailableException, NotCoherentException, NotAllDataException, NotSuchProductException, NotSuchUserException {
		if(checkTravelPackageCoherence()) {
			travelPackage.setTimestampComposition(new Timestamp(new Date().getTime()));
			int packageId = travelPackageService.addPackage(travelPackage);
			entityManager.clear();
			participationService.confirmParticipation(accountService.getCurrentUser().getId(), packageId, reservations);
			return packageId;
		} else throw new NotCoherentException();
	}
	
	@RolesAllowed({UserRole._EMPLOYEE})
	@Override
	public int composePredefinedPackage(Date expiry, BigDecimal basePrice)
			throws NotAvailableException, NotCoherentException, NotAllDataException, NotSuchProductException {
		if(expiry != null && basePrice != null) {
			travelPackage.setExpiry(expiry);
			travelPackage.setBasePrice(basePrice);
		} else throw new NotAllDataException();
		if(checkTravelPackageCoherence()) {
			travelPackage.setAdderEmployee(accountService.getCurrentUser());
			travelPackage.setTimestampComposition(new Timestamp(new Date().getTime()));
			int packageId = travelPackageService.addPackage(travelPackage);
			return packageId;
		} else throw new NotCoherentException();
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public List<TransportDTO> getTransports() throws NotAllDataException{
		Date start = travelPackage.getStartTrip();
		Date end = travelPackage.getEndTrip();
		if(start != null && end != null && reservations > 0) {
			List<TransportDTO> transports = baseProductService.getAvailableTransports(start, end, reservations);
			return transports != null ? transports : new ArrayList<TransportDTO>();
		} else throw new NotAllDataException();
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public List<HotelDTO> getHotels() throws NotAllDataException {
		Date start = travelPackage.getStartTrip();
		Date end = travelPackage.getEndTrip();
		if(start != null && end != null && reservations > 0) {
			List<HotelDTO> hotels = baseProductService.getAvailableHotels(start, end, reservations);
			return hotels != null ? hotels : new ArrayList<HotelDTO>();
		} else throw new NotAllDataException();
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public List<ExcursionDTO> getExcursions() throws NotAllDataException {
		Date start = travelPackage.getStartTrip();
		Date end = travelPackage.getEndTrip();
		if(start != null && end != null && reservations > 0) {
			List<ExcursionDTO> excursions = baseProductService.getAvailableExcursions(start, end, reservations);
			return excursions != null ? excursions : new ArrayList<ExcursionDTO>();
		} else throw new NotAllDataException();
	}
	
	private boolean isTemporalIntervalTripCoherent(Date start, Date end) throws NotAllDataException {
		if(start != null && end != null) {
			Date currentDate = new Date();
			switch(travelPackage.getType()) {
			case CUSTOM:
				return  currentDate.before(travelPackage.getExpiry()) && travelPackage.getExpiry().before(start)
						&& start.before(end);
			case PREDEFINED:
				if(travelPackage.getExpiry() != null) {
					return  currentDate.before(travelPackage.getExpiry()) && travelPackage.getExpiry().before(start)
							&& start.before(end);
				} else {
					return currentDate.before(start) && start.before(end);
				}
			default:
				return false;
			}
		} else throw new NotAllDataException();
	}
	
	private boolean checkTravelPackageCoherence()
			throws NotAllDataException, NotSuchProductException, NotAvailableException, NotCoherentException {
		List<HotelDTO> hotels = travelPackage.getHotels();
		List<TransportDTO> transports = travelPackage.getTransports();
		List<ExcursionDTO> excursions = travelPackage.getExcursions();
		if(reservations > 0 && !(hotels.isEmpty() && transports.isEmpty() && excursions.isEmpty())){
			if(checkTransportsCoherence(transports) && checkHotelsCoherence(hotels) && checkExcursionsCoherence(excursions)) {
				if(isTemporalIntervalTripCoherent(travelPackage.getStartTrip(), travelPackage.getEndTrip())) {
					for(HotelDTO h : hotels) {
						if(!baseProductService.isAvailableFor(h.getId(), BaseProductType.HOTEL, reservations)) {
							throw new NotAvailableException();
						}
					}
					for(TransportDTO t : transports) {
						if(!baseProductService.isAvailableFor(t.getId(), BaseProductType.TRANSPORT, reservations)) {
							throw new NotAvailableException();
						}
					}
					for(ExcursionDTO e : excursions) {
						if(!baseProductService.isAvailableFor(e.getId(), BaseProductType.EXCURSION, reservations)) {
							throw new NotAvailableException();
						}
					}
					return true;
				}
			}
		} else throw new NotAllDataException();
		return false;
	}

	private boolean checkExcursionsCoherence(List<ExcursionDTO> excursions) throws NotSuchProductException {
		ExcursionDTO excursion;
		for(ExcursionDTO e : excursions) {
			excursion = baseProductService.getExcursion(e.getId());
			if(!(excursion.getStartTrip().after(travelPackage.getStartTrip()) || excursion.getStartTrip().equals(travelPackage.getStartTrip()))
					|| !(excursion.getEndTrip().before(travelPackage.getEndTrip()) || excursion.getEndTrip().equals(travelPackage.getEndTrip()))) {
				return false;
			}
		}
		return true;
	}

	private boolean checkHotelsCoherence(List<HotelDTO> hotels) throws NotSuchProductException {
		HotelDTO hotel;
		switch(hotels.size()) {
		case 0:
			return true;
		case 1:
			hotel = baseProductService.getHotel(hotels.get(0).getId());
			return (hotel.getStartTrip().after(travelPackage.getStartTrip()) || hotel.getStartTrip().equals(travelPackage.getStartTrip()))
					&& (hotel.getEndTrip().before(travelPackage.getEndTrip()) || hotel.getEndTrip().equals(travelPackage.getEndTrip()));
		default:
			return false;
		}
	}

	private boolean checkTransportsCoherence(List<TransportDTO> transports) throws NotSuchProductException {
		TransportDTO transport1;
		TransportDTO transport2;
		switch(transports.size()) {
		case 0:
			return true;
		case 1:
			transport1 = baseProductService.getTransport(transports.get(0).getId());
			return (transport1.getStartTrip().after(travelPackage.getStartTrip()) || transport1.getStartTrip().equals(travelPackage.getStartTrip()))
					&& (transport1.getEndTrip().before(travelPackage.getEndTrip()) || transport1.getEndTrip().equals(travelPackage.getEndTrip()));
		case 2:
			transport1 = baseProductService.getTransport(transports.get(0).getId());
			transport2 = baseProductService.getTransport(transports.get(1).getId());
			return (transport1.getStartTrip().after(travelPackage.getStartTrip()) || transport1.getStartTrip().equals(travelPackage.getStartTrip()))
					&& (transport1.getEndTrip().before(travelPackage.getEndTrip()) ||  transport1.getEndTrip().equals(travelPackage.getEndTrip()))
					&& (transport2.getStartTrip().after(travelPackage.getStartTrip()) || transport2.getStartTrip().equals(travelPackage.getStartTrip()))
					&& (transport2.getEndTrip().before(travelPackage.getEndTrip()) || transport2.getEndTrip().equals(travelPackage.getEndTrip()))
					&& (transport1.getEndTrip().before(transport2.getStartTrip())
							|| transport2.getEndTrip().before(transport1.getStartTrip()));
		default:
			return false;
		}
	}

}
